#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
天翼云盘PC版模拟客户端
基于189pc Go代码实现的Python版本
支持登录、文件管理、上传下载等功能
"""

import requests
import hashlib
import hmac
import time
import uuid
import re
import json
import os
import base64
import xml.etree.ElementTree as ET
from typing import Dict, List, Optional, Any, Union
from urllib.parse import urlparse, parse_qs, urlencode, quote
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5, AES
from Crypto.Util.Padding import pad
import binascii
import logging
import io
import math
from concurrent.futures import ThreadPoolExecutor, as_completed

# 常量定义 (基于Go代码)
ACCOUNT_TYPE = "02"
APP_ID = "8025431004"
CLIENT_TYPE = "10020"
VERSION = "6.2"

WEB_URL = "https://cloud.189.cn"
AUTH_URL = "https://open.e.189.cn"
API_URL = "https://api.cloud.189.cn"
UPLOAD_URL = "https://upload.cloud.189.cn"

RETURN_URL = "https://m.cloud.189.cn/zhuanti/2020/loginErrorPc/index.html"

PC = "TELEPC"
MAC = "TELEMAC"

CHANNEL_ID = "web_cloud.189.cn"

class Cloud189PCClient:
    """天翼云盘PC版客户端"""
    
    def __init__(self, cache_file: str = "data/db/cache_189.json", enable_debug: bool = True):
        self.session = requests.Session()
        # 设置基础请求头
        self.session.headers.update({
            'Accept': 'application/json;charset=UTF-8',
            'Referer': WEB_URL,
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        })
        
        self.cache_file = cache_file
        self.token_info = None
        self.login_param = None
        self.family_id = None
        self.identity = None
        self.enable_debug = enable_debug
        
        # 设置日志
        if enable_debug:
            logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
            self.logger = logging.getLogger(__name__)
        else:
            self.logger = logging.getLogger(__name__)
            self.logger.setLevel(logging.WARNING)
        
        # 加载缓存
        self.load_cache()
    
    def client_suffix(self) -> Dict[str, str]:
        """客户端后缀参数"""
        import random
        rand_num = random.randint(10000, 99999)
        rand_num2 = random.randint(1000000000, 9999999999)
        return {
            "clientType": PC,
            "version": VERSION,
            "channelId": CHANNEL_ID,
            "rand": f"{rand_num}_{rand_num2}"
        }
    
    def get_http_date_str(self) -> str:
        """获取HTTP规范时间"""
        return time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime())
    
    def timestamp(self) -> int:
        """获取时间戳(毫秒)"""
        return int(time.time() * 1000)
    
    def signature_of_hmac(self, session_secret: str, session_key: str, operate: str, 
                         full_url: str, date_of_gmt: str, params: str = "") -> str:
        """HMAC签名"""
        url_path = re.search(r'://[^/]+((/[^/\s?#]+)*)', full_url).group(1)
        data = f"SessionKey={session_key}&Operate={operate}&RequestURI={url_path}&Date={date_of_gmt}"
        if params:
            data += f"&params={params}"
        
        signature = hmac.new(
            session_secret.encode(), 
            data.encode(), 
            hashlib.sha1
        ).hexdigest().upper()
        return signature
    
    def rsa_encrypt(self, public_key: str, orig_data: str) -> str:
        """RSA加密用户名密码（参考Go源码实现）"""
        try:
            # 处理公钥格式
            if not public_key.startswith('-----BEGIN'):
                public_key = f"-----BEGIN PUBLIC KEY-----\n{public_key}\n-----END PUBLIC KEY-----"
            
            rsa_key = RSA.import_key(public_key)
            cipher = PKCS1_v1_5.new(rsa_key)
            encrypted = cipher.encrypt(orig_data.encode())
            # 转换为大写十六进制字符串（与Go源码保持一致）
            return binascii.hexlify(encrypted).decode().upper()
        except Exception as e:
            print(f"RSA加密失败: {e}")
            if self.enable_debug:
                self.logger.error(f"RSA加密异常: {e}")
            return ""
    
    def aes_ecb_encrypt(self, data: str, key: str) -> str:
        """AES ECB加密params"""
        try:
            cipher = AES.new(key[:16].encode(), AES.MODE_ECB)
            padded_data = pad(data.encode(), AES.block_size)
            encrypted = cipher.encrypt(padded_data)
            return binascii.hexlify(encrypted).decode().upper()
        except Exception as e:
            print(f"AES加密失败: {e}")
            return ""
    
    def signature_header(self, url: str, method: str, params: str = "", is_family: bool = False) -> Dict[str, str]:
        """生成签名头"""
        if not self.token_info:
            return {}
        
        date_of_gmt = self.get_http_date_str()
        session_key = self.token_info.get('sessionKey', '')
        session_secret = self.token_info.get('sessionSecret', '')
        
        if is_family:
            session_key = self.token_info.get('familySessionKey', '')
            session_secret = self.token_info.get('familySessionSecret', '')
        
        return {
            "Date": date_of_gmt,
            "SessionKey": session_key,
            "X-Request-ID": str(uuid.uuid4()),
            "Signature": self.signature_of_hmac(session_secret, session_key, method, url, date_of_gmt, params)
        }
    
    def encrypt_params(self, params: Dict[str, str], is_family: bool = False) -> str:
        """加密参数"""
        if not self.token_info or not params:
            return ""
        
        session_secret = self.token_info.get('sessionSecret', '')
        if is_family:
            session_secret = self.token_info.get('familySessionSecret', '')
        
        # 将参数编码为URL格式
        param_str = '&'.join([f"{k}={v}" for k, v in params.items()])
        return self.aes_ecb_encrypt(param_str, session_secret)
    
    def load_cache(self):
        """从缓存文件加载登录信息（检查缓存有效性）"""
        try:
            if os.path.exists(self.cache_file):
                with open(self.cache_file, 'r', encoding='utf-8') as f:
                    cache_data = json.load(f)
                
                # 检查缓存时间戳（缓存有效期7天）
                cache_timestamp = cache_data.get('timestamp', 0)
                current_time = int(time.time())
                cache_age_days = (current_time - cache_timestamp) / (24 * 3600)
                
                if cache_age_days > 7:
                    print(f"缓存已过期（{cache_age_days:.1f}天），清理缓存")
                    self.clear_cache()
                    return False
                
                self.token_info = cache_data.get('token_info')
                self.family_id = cache_data.get('family_id')
                self.identity = cache_data.get('identity')
                
                if self.token_info:
                    print(f"已从缓存加载登录信息: {self.token_info.get('loginName', 'Unknown')}")
                    return True
            else:
                print(f"缓存文件 {self.cache_file} 不存在")
        except Exception as e:
            print(f"加载缓存失败: {e}")
            self.clear_cache()
        return False
    
    def clear_cache(self):
        """清理缓存"""
        try:
            if os.path.exists(self.cache_file):
                os.remove(self.cache_file)
            self.token_info = None
            self.family_id = None
            self.identity = None
        except Exception as e:
            print(f"清理缓存失败: {e}")
    
    def save_cache(self):
        """保存缓存（仅保存必要的登录信息，不保存cookie）"""
        cache_data = {
            'token_info': self.token_info,
            'identity': self.identity,
            'family_id': self.family_id,
            'timestamp': int(time.time())  # 添加时间戳用于缓存过期检查
        }
        
        try:
            with open(self.cache_file, 'w', encoding='utf-8') as f:
                json.dump(cache_data, f, ensure_ascii=False, indent=2)
            print(f"登录信息已保存到 {self.cache_file}")
        except Exception as e:
            print(f"保存缓存失败: {e}")
    
    def is_login(self) -> bool:
        """检查是否已登录"""
        if self.token_info is None:
            return False
        
        # 通过调用getUserInfo接口验证登录状态（参考Go源码）
        try:
            url = f"{API_URL}/getUserInfo.action"
            params = self.client_suffix()
            headers = self.signature_header(url, "GET")
            response = self.session.get(url, params=params, headers=headers)
            return response.status_code == 200
        except Exception:
            return False
    
    def get_encrypt_conf(self) -> Optional[Dict[str, Any]]:
        """获取加密配置"""
        url = f"{AUTH_URL}/api/logbox/config/encryptConf.do"
        data = {'appId': APP_ID}
        headers = {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
        
        if self.enable_debug:
            self.logger.debug(f"请求URL: {url}")
            self.logger.debug(f"请求数据: {data}")
            self.logger.debug(f"请求头: {headers}")
        
        try:
            response = self.session.post(url, data=data, headers=headers)
            if self.enable_debug:
                self.logger.debug(f"响应状态码: {response.status_code}")
                self.logger.debug(f"响应内容: {response.text}")
            
            if response.status_code == 200:
                return response.json()
        except Exception as e:
            print(f"获取加密配置失败: {e}")
            if self.enable_debug:
                self.logger.error(f"获取加密配置异常: {e}")
        return None
    
    def get_login_param(self) -> bool:
        """获取登录参数"""
        # 清除cookie
        self.session.cookies.clear()
        
        # 获取登录页面参数
        login_url = f"{WEB_URL}/api/portal/unifyLoginForPC.action"
        params = {
            'appId': APP_ID,
            'clientType': CLIENT_TYPE,
            'returnURL': RETURN_URL,
            'timeStamp': str(self.timestamp())
        }
        
        if self.enable_debug:
            self.logger.debug(f"请求URL: {login_url}")
            self.logger.debug(f"请求参数: {params}")
        
        try:
            response = self.session.get(login_url, params=params)
            if self.enable_debug:
                self.logger.debug(f"响应状态码: {response.status_code}")
                self.logger.debug(f"响应内容: {response.text[:500]}...")  # 只显示前500字符
            
            if response.status_code == 200:
                content = response.text
                # 使用正则表达式提取参数
                captcha_token_match = re.search(r"'captchaToken' value='(.+?)'", content)
                lt_match = re.search(r'lt = "(.+?)"', content)
                param_id_match = re.search(r'paramId = "(.+?)"', content)
                req_id_match = re.search(r'reqId = "(.+?)"', content)
                
                if not all([captcha_token_match, lt_match, param_id_match, req_id_match]):
                    print("解析登录页面参数失败")
                    return False
                
                # 获取加密配置
                encrypt_conf = self.get_encrypt_conf()
                if not encrypt_conf or encrypt_conf.get('result') != 0:
                    print("获取加密配置失败")
                    return False
                
                pub_key = encrypt_conf['data']['pubKey']
                pre = encrypt_conf['data']['pre']
                
                # 构建完整的RSA公钥
                rsa_key = f"-----BEGIN PUBLIC KEY-----\n{pub_key}\n-----END PUBLIC KEY-----"
                
                self.login_param = {
                    'captchaToken': captcha_token_match.group(1),
                    'lt': lt_match.group(1),
                    'paramId': param_id_match.group(1),
                    'reqId': req_id_match.group(1),
                    'jRsaKey': rsa_key,
                    'pre': pre
                }
                return True
        except Exception as e:
            print(f"获取登录参数失败: {e}")
        return False
    
    def auto_login(self) -> bool:
        """自动登录（使用缓存的凭据）"""
        if not hasattr(self, '_cached_username') or not hasattr(self, '_cached_password'):
            return False
        
        print("尝试自动重新登录...")
        return self.login(self._cached_username, self._cached_password)
    
    def login(self, username: str = None, password: str = None, captcha: str = None) -> bool:
        """登录"""
        # 如果没有提供用户名密码，尝试从用户输入获取
        if not username:
            username = input("请输入用户名: ").strip()
        if not password:
            password = input("请输入密码: ").strip()
        
        # 验证用户名密码不能为空（参考Go源码中的验证逻辑）
        if not username or not password:
            print("错误: 用户名和密码不能为空")
            return False
        
        # 缓存用户名密码用于自动重新登录（仅在内存中，不保存到文件）
        self._cached_username = username
        self._cached_password = password
        
        # 生成身份标识（参考Go源码中的identity生成方式）
        new_identity = hashlib.md5((username + password).encode()).hexdigest()
        
        # 如果已登录且身份相同，直接返回
        if self.is_login() and self.identity == new_identity:
            print("已登录，无需重复登录")
            return True
        
        # 获取登录参数
        if not self.get_login_param():
            return False
        
        # RSA加密用户名和密码
        rsa_username = self.rsa_encrypt(self.login_param['jRsaKey'], username)
        rsa_password = self.rsa_encrypt(self.login_param['jRsaKey'], password)
        
        if not rsa_username or not rsa_password:
            print("加密用户名密码失败")
            return False
        
        # 添加前缀
        pre = self.login_param['pre']
        rsa_username = pre + rsa_username
        rsa_password = pre + rsa_password
        
        # 登录请求
        login_url = f"{AUTH_URL}/api/logbox/oauth2/loginSubmit.do"
        login_data = {
            'appKey': APP_ID,
            'accountType': ACCOUNT_TYPE,
            'userName': rsa_username,
            'password': rsa_password,
            'validateCode': captcha or '',
            'captchaToken': self.login_param.get('captchaToken', ''),
            'returnUrl': RETURN_URL,
            'dynamicCheck': 'FALSE',
            'clientType': CLIENT_TYPE,
            'cb_SaveName': '1',
            'isOauth2': 'false',
            'state': '',
            'paramId': self.login_param['paramId']
        }
        
        headers = {
            'REQID': self.login_param['reqId'],
            'lt': self.login_param['lt']
            # 不设置Content-Type，让requests自动处理form-data
        }
        
        if self.enable_debug:
            self.logger.debug(f"登录请求URL: {login_url}")
            # 隐藏敏感信息
            safe_data = login_data.copy()
            safe_data['userName'] = f"***[长度:{len(login_data['userName'])}]"
            safe_data['password'] = f"***[长度:{len(login_data['password'])}]"
            self.logger.debug(f"登录请求数据: {safe_data}")
            self.logger.debug(f"登录请求头: {headers}")
            # 检查加密后的用户名密码是否为空
            if not login_data['userName'] or not login_data['password']:
                self.logger.error(f"加密后的用户名或密码为空！userName长度: {len(login_data['userName'])}, password长度: {len(login_data['password'])}")
            else:
                self.logger.debug(f"加密后用户名密码长度正常: userName={len(login_data['userName'])}, password={len(login_data['password'])}")
        
        try:
            # 使用form-data方式发送请求（参考Go源码）
            response = self.session.post(login_url, data=login_data, headers=headers)
            if self.enable_debug:
                self.logger.debug(f"登录响应状态码: {response.status_code}")
                self.logger.debug(f"登录响应内容: {response.text}")
            if response.status_code == 200:
                result = response.json()
                if result.get('result') == 0:
                    # 登录成功，获取重定向URL
                    to_url = result.get('toUrl')
                    if to_url:
                        # 获取Session
                        session_url = f"{API_URL}/getSessionForPC.action"
                        session_params = self.client_suffix()
                        session_params['redirectURL'] = to_url
                        
                        if self.enable_debug:
                            self.logger.debug(f"获取Session URL: {session_url}")
                            self.logger.debug(f"获取Session 参数: {session_params}")
                        
                        session_response = self.session.post(session_url, params=session_params)
                        if self.enable_debug:
                            self.logger.debug(f"Session响应状态码: {session_response.status_code}")
                            self.logger.debug(f"Session响应内容: {session_response.text}")
                        
                        if session_response.status_code == 200:
                            session_result = session_response.json()
                            # 检查不同的响应字段（Go源码中使用resCode，但实际可能是res_code）
                            res_code = session_result.get('resCode') or session_result.get('res_code')
                            if res_code == 0:
                                self.token_info = session_result
                                self.identity = new_identity
                                self.save_cache()
                                print(f"登录成功: {self.token_info.get('loginName', username)}")
                                return True
                            else:
                                res_message = session_result.get('resMessage') or session_result.get('res_message')
                                print(f"获取session失败: {res_message}")
                else:
                    print(f"登录失败: {result.get('msg', '未知错误')}")
                    # 如果需要验证码，提示用户输入
                    if '验证码' in result.get('msg', ''):
                        captcha = input("请输入验证码: ")
                        return self.login(username, password, captcha)
        except Exception as e:
            print(f"登录请求失败: {e}")
        
        return False
    
    def refresh_session(self) -> bool:
        """刷新session（失败时自动重新登录）"""
        if not self.token_info or 'accessToken' not in self.token_info:
            print("没有有效的accessToken，尝试重新登录")
            return self.auto_login()
            
        url = f"{API_URL}/getSessionForPC.action"
        params = self.client_suffix()
        params.update({
            'appId': APP_ID,
            'accessToken': self.token_info['accessToken']
        })
        
        headers = {
            'X-Request-ID': str(uuid.uuid4())
        }
        
        if self.enable_debug:
            self.logger.debug(f"刷新Session URL: {url}")
            self.logger.debug(f"刷新Session 参数: {params}")
            self.logger.debug(f"刷新Session 请求头: {headers}")
        
        try:
            response = self.session.get(url, params=params, headers=headers)
            if self.enable_debug:
                self.logger.debug(f"刷新Session响应状态码: {response.status_code}")
                self.logger.debug(f"刷新Session响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                if result.get('res_code') == 0:
                    # 更新token信息
                    self.token_info.update(result)
                    self.save_cache()
                    print("Session刷新成功")
                    return True
                else:
                    print(f"刷新session失败: {result.get('res_message')}")
                    # 如果token无效，清理缓存并尝试重新登录
                    if result.get('resCode') == 'UserInvalidOpenToken':
                        print("Token已失效，清理缓存并重新登录")
                        self.clear_cache()
                    return self.auto_login()
            else:
                print(f"Session刷新请求失败: {response.status_code}")
                return self.auto_login()
        except Exception as e:
            print(f"刷新session失败: {e}")
            return self.auto_login()
    
    def get_family_id(self) -> str:
        """获取家庭云ID"""
        if self.family_id:
            return self.family_id
        
        url = f"{API_URL}/family/manage/getFamilyList.action"
        headers = self.signature_header(url, "GET")
        params = self.client_suffix()
        
        if self.enable_debug:
            self.logger.debug(f"获取家庭云ID URL: {url}")
            self.logger.debug(f"获取家庭云ID 参数: {params}")
            self.logger.debug(f"获取家庭云ID 请求头: {headers}")
        
        try:
            response = self.session.get(url, headers=headers, params=params)
            if self.enable_debug:
                self.logger.debug(f"获取家庭云ID响应状态码: {response.status_code}")
                self.logger.debug(f"获取家庭云ID响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                family_list = result.get('familyInfoResp', [])
                if family_list:
                    self.family_id = str(family_list[0]['familyId'])
                    return self.family_id
        except Exception as e:
            print(f"获取家庭云ID失败: {e}")
        return ""
    
    def get_files(self, folder_id: str = "-11", is_family: bool = False, 
                 page_num: int = 1, page_size: int = 1000) -> Optional[Dict[str, Any]]:
        """获取文件列表
        
        返回完整的API响应数据，包含以下结构：
        {
            "res_code": 0,  # 响应状态码，0表示成功
            "res_message": "成功",  # 响应消息
            "fileListAO": {
                "count": 17,  # 总项目数量
                "fileList": [...],  # 文件列表
                "fileListSize": 10,  # 文件数量
                "folderList": [...]  # 文件夹列表
            },
            "lastRev": 20250726222758  # 最后版本号
        }
        
        Args:
            folder_id: 文件夹ID，默认为根目录(-11)
            is_family: 是否为家庭云模式
            page_num: 页码，从1开始
            page_size: 每页大小，默认1000
            
        Returns:
            完整的API响应数据字典，失败时返回None
        """
        if not self.is_login():
            print("未登录，无法获取文件列表")
            return None
        
        if is_family:
            url = f"{API_URL}/family/file/listFiles.action"
            if not self.family_id:
                self.family_id = self.get_family_id()
        else:
            url = f"{API_URL}/listFiles.action"
        
        headers = self.signature_header(url, "GET", "", is_family)
        params = self.client_suffix()
        
        # 基础参数（参考Go源码getFilesWithPage方法）
        params.update({
            'folderId': folder_id,
            'fileType': '0',
            'mediaAttr': '0', 
            'iconOption': '5',
            'pageNum': str(page_num),
            'pageSize': str(page_size)
        })
        
        if is_family:
            params.update({
                'familyId': self.family_id,
                'orderBy': self._to_family_order_by('filename'),
                'descending': self._to_desc('asc')
            })
        else:
            params.update({
                'recursive': '0',
                'orderBy': 'filename',
                'descending': self._to_desc('asc')
            })
        
        if self.enable_debug:
            self.logger.debug(f"获取文件列表 URL: {url}")
            self.logger.debug(f"获取文件列表 参数: {params}")
            self.logger.debug(f"获取文件列表 请求头: {headers}")
        
        try:
            response = self.session.get(url, headers=headers, params=params)
            if self.enable_debug:
                self.logger.debug(f"获取文件列表响应状态码: {response.status_code}")
                # 不输出响应内容，避免混入界面显示
                # self.logger.debug(f"获取文件列表响应内容: {response.text}")
            
            if response.status_code == 200:
                return response.json()
        except Exception as e:
            print(f"获取文件列表失败: {e}")
            if self.enable_debug:
                self.logger.error(f"获取文件列表异常: {e}")
        return None
    
    def _to_family_order_by(self, order_by: str) -> str:
        """转换家庭云排序字段（参考Go源码toFamilyOrderBy函数）"""
        mapping = {
            'filename': '1',
            'filesize': '2', 
            'lastOpTime': '3'
        }
        return mapping.get(order_by, '1')
    
    def _to_desc(self, order_direction: str) -> str:
        """转换排序方向（参考Go源码toDesc函数）"""
        if order_direction.lower() == 'desc':
            return 'true'
        return 'false'
    
    def parse_file_info(self, file_data: Dict[str, Any]) -> Dict[str, Any]:
        """解析文件信息，提供完整的字段说明和格式化
        
        Args:
            file_data: 原始文件数据字典
            
        Returns:
            格式化后的文件信息字典
        """
        if not file_data:
            return {}
        
        # 文件类型映射
        file_cata_map = {
            1: '图片',
            2: '视频', 
            3: '音频',
            4: '文档',
            5: '其他'
        }
        
        media_type_map = {
            1: '图片',
            2: '视频',
            3: '音频', 
            4: '文档'
        }
        
        star_label_map = {
            1: '已标星',
            2: '未标星'
        }
        
        # 格式化文件大小
        size = int(file_data.get('size', 0))
        if size > 1024 * 1024 * 1024:
            size_str = f"{size / (1024 * 1024 * 1024):.2f} GB"
        elif size > 1024 * 1024:
            size_str = f"{size / (1024 * 1024):.2f} MB"
        elif size > 1024:
            size_str = f"{size / 1024:.2f} KB"
        else:
            size_str = f"{size} bytes"
        
        parsed_info = {
            # 基础信息
            'id': str(file_data.get('id', '')),
            'name': file_data.get('name', ''),
            'size': size,
            'size_formatted': size_str,
            
            # 时间信息
            'create_date': file_data.get('createDate', ''),
            'last_op_time': file_data.get('lastOpTime', ''),
            'revision': file_data.get('rev', ''),
            
            # 文件分类
            'file_cata': file_data.get('fileCata', 0),
            'file_cata_name': file_cata_map.get(file_data.get('fileCata', 0), '未知'),
            'media_type': file_data.get('mediaType', 0),
            'media_type_name': media_type_map.get(file_data.get('mediaType', 0), '未知'),
            
            # 文件属性
            'md5': file_data.get('md5', ''),
            'star_label': file_data.get('starLabel', 2),
            'star_label_name': star_label_map.get(file_data.get('starLabel', 2), '未标星'),
            
            # 图片特有属性
            'orientation': file_data.get('orientation', 0),
            
            # 图标和预览
            'icon': file_data.get('icon', {}),
            'large_icon_url': file_data.get('icon', {}).get('largeUrl', ''),
            'small_icon_url': file_data.get('icon', {}).get('smallUrl', ''),
            
            # 父目录信息
            'parent_id': str(file_data.get('parentId', '')),
            
            # 原始数据
            'raw_data': file_data
        }
        
        return parsed_info
    
    def parse_folder_info(self, folder_data: Dict[str, Any]) -> Dict[str, Any]:
        """解析文件夹信息，提供完整的字段说明和格式化
        
        Args:
            folder_data: 原始文件夹数据字典
            
        Returns:
            格式化后的文件夹信息字典
        """
        if not folder_data:
            return {}
        
        star_label_map = {
            1: '已标星',
            2: '未标星'
        }
        
        parsed_info = {
            # 基础信息
            'id': str(folder_data.get('id', '')),
            'name': folder_data.get('name', ''),
            
            # 时间信息
            'create_date': folder_data.get('createDate', ''),
            'last_op_time': folder_data.get('lastOpTime', ''),
            'revision': folder_data.get('rev', ''),
            
            # 文件夹统计
            'file_count': folder_data.get('fileCount', 0),
            'file_list_size': folder_data.get('fileListSize', 0),
            
            # 文件夹属性
            'file_cata': folder_data.get('fileCata', 1),  # 文件夹固定为1
            'star_label': folder_data.get('starLabel', 2),
            'star_label_name': star_label_map.get(folder_data.get('starLabel', 2), '未标星'),
            
            # 父目录信息
            'parent_id': str(folder_data.get('parentId', '')),
            
            # 原始数据
            'raw_data': folder_data
        }
        
        return parsed_info
    
    def parse_files_response(self, response_data: Dict[str, Any]) -> Dict[str, Any]:
        """解析文件列表响应数据，提供完整的结构化信息
        
        Args:
            response_data: get_files方法返回的原始响应数据
            
        Returns:
            结构化的文件列表信息
        """
        if not response_data:
            return {}
        
        file_list_ao = response_data.get('fileListAO', {})
        
        # 解析文件列表
        files = []
        for file_data in file_list_ao.get('fileList', []):
            files.append(self.parse_file_info(file_data))
        
        # 解析文件夹列表
        folders = []
        for folder_data in file_list_ao.get('folderList', []):
            folders.append(self.parse_folder_info(folder_data))
        
        # 统计信息
        file_type_stats = {}
        media_type_stats = {}
        total_size = 0
        
        for file_info in files:
            # 文件类型统计
            file_cata = file_info['file_cata']
            file_type_stats[file_cata] = file_type_stats.get(file_cata, 0) + 1
            
            # 媒体类型统计
            media_type = file_info['media_type']
            media_type_stats[media_type] = media_type_stats.get(media_type, 0) + 1
            
            # 总大小统计
            total_size += file_info['size']
        
        # 格式化总大小
        if total_size > 1024 * 1024 * 1024:
            total_size_str = f"{total_size / (1024 * 1024 * 1024):.2f} GB"
        elif total_size > 1024 * 1024:
            total_size_str = f"{total_size / (1024 * 1024):.2f} MB"
        elif total_size > 1024:
            total_size_str = f"{total_size / 1024:.2f} KB"
        else:
            total_size_str = f"{total_size} bytes"
        
        parsed_response = {
            # 响应状态
            'success': response_data.get('res_code', -1) == 0,
            'res_code': response_data.get('res_code', -1),
            'res_message': response_data.get('res_message', ''),
            'last_revision': response_data.get('lastRev', 0),
            
            # 文件和文件夹列表
            'files': files,
            'folders': folders,
            
            # 统计信息
            'statistics': {
                'total_count': file_list_ao.get('count', 0),
                'file_count': len(files),
                'folder_count': len(folders),
                'total_size': total_size,
                'total_size_formatted': total_size_str,
                'file_type_stats': file_type_stats,
                'media_type_stats': media_type_stats
            },
            
            # 原始数据
            'raw_response': response_data
        }
        
        return parsed_response
    
    def get_download_url(self, file_id: str, is_family: bool = False) -> Optional[str]:
        """获取下载链接"""
        if not self.is_login():
            print("未登录，无法获取下载链接")
            return None
        
        if is_family:
            url = f"{API_URL}/family/file/getFileDownloadUrl.action"
        else:
            url = f"{API_URL}/getFileDownloadUrl.action"
        
        headers = self.signature_header(url, "GET", "", is_family)
        params = self.client_suffix()
        params['fileId'] = file_id
        
        if is_family:
            params['familyId'] = self.family_id
        else:
            params.update({
                'dt': '3',
                'flag': '1'
            })
        
        if self.enable_debug:
            self.logger.debug(f"获取下载链接 URL: {url}")
            self.logger.debug(f"获取下载链接 参数: {params}")
            self.logger.debug(f"获取下载链接 请求头: {headers}")
        
        try:
            response = self.session.get(url, headers=headers, params=params)
            if self.enable_debug:
                self.logger.debug(f"获取下载链接响应状态码: {response.status_code}")
                self.logger.debug(f"获取下载链接响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                download_url = result.get('fileDownloadUrl')
                if download_url:
                    # 处理URL格式
                    download_url = download_url.replace('&amp;', '&').replace('http://', 'https://')
                    
                    # 处理重定向获取真实下载链接
                    try:
                        redirect_response = self.session.get(download_url, allow_redirects=False)
                        if redirect_response.status_code == 302:
                            real_download_url = redirect_response.headers.get('location')
                            if real_download_url:
                                download_url = real_download_url
                    except Exception:
                        pass
                    
                    return download_url
        except Exception as e:
            print(f"获取下载链接失败: {e}")
        return None
    
    def download_file(self, file_id: str, save_path: str, is_family: bool = False, filename: str = None) -> bool:
        """下载文件"""
        download_url = self.get_download_url(file_id, is_family)
        if not download_url:
            print("无法获取下载链接")
            return False
        
        try:
            # 如果save_path是目录，需要添加文件名
            if os.path.isdir(save_path) or save_path.endswith('\\') or save_path.endswith('/'):
                if not filename:
                    filename = f"file_{file_id}"
                save_path = os.path.join(save_path, filename)
            
            # 如果文件已存在，自动重命名
            if os.path.exists(save_path):
                base_name, ext = os.path.splitext(save_path)
                counter = 1
                while os.path.exists(f"{base_name}({counter}){ext}"):
                    counter += 1
                save_path = f"{base_name}({counter}){ext}"
                print(f"文件已存在，自动重命名为: {os.path.basename(save_path)}")
            
            # 确保目录存在
            parent_dir = os.path.dirname(save_path)
            if parent_dir:
                os.makedirs(parent_dir, exist_ok=True)
            
            print(f"开始下载文件到: {save_path}")
            response = self.session.get(download_url, stream=True)
            
            if response.status_code == 200:
                with open(save_path, 'wb') as f:
                    for chunk in response.iter_content(chunk_size=8192):
                        if chunk:
                            f.write(chunk)
                
                print(f"文件下载完成: {save_path}")
                return True
            else:
                print(f"下载失败，状态码: {response.status_code}")
                return False
                
        except Exception as e:
            print(f"下载文件失败: {e}")
            return False
    
    def make_dir(self, parent_id: str, dir_name: str, is_family: bool = False) -> Optional[str]:
        """创建文件夹"""
        if not self.is_login():
            print("未登录，无法创建文件夹")
            return None
        
        if is_family:
            url = f"{API_URL}/family/file/createFolder.action"
        else:
            url = f"{API_URL}/createFolder.action"
        
        headers = self.signature_header(url, "POST", "", is_family)
        data = self.client_suffix()
        data.update({
            'parentFolderId': parent_id,
            'folderName': dir_name
        })
        
        if is_family:
            data['familyId'] = self.family_id
        
        if self.enable_debug:
            self.logger.debug(f"创建文件夹 URL: {url}")
            self.logger.debug(f"创建文件夹 数据: {data}")
            self.logger.debug(f"创建文件夹 请求头: {headers}")
        
        try:
            response = self.session.post(url, headers=headers, data=data)
            if self.enable_debug:
                self.logger.debug(f"创建文件夹响应状态码: {response.status_code}")
                self.logger.debug(f"创建文件夹响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                if result.get('res_code') == 0:
                    folder_id = result.get('id')
                    print(f"文件夹创建成功: {dir_name} (ID: {folder_id})")
                    return folder_id
                else:
                    print(f"创建文件夹失败: {result.get('res_message')}")
        except Exception as e:
            print(f"创建文件夹失败: {e}")
        return None
    
    def calculate_slice_size(self, file_size: int) -> int:
        """计算分片大小（与Go代码partSize函数保持一致）"""
        import math
        
        DEFAULT = 1024 * 1024 * 10  # 10MIB
        
        if file_size > DEFAULT * 2 * 999:
            # 超过20GB的文件，使用更大的分片
            return int(max(math.ceil((file_size / 1999) / DEFAULT), 5) * DEFAULT)
        elif file_size > DEFAULT * 999:
            # 超过10GB的文件，使用20MB分片
            return DEFAULT * 2  # 20MIB
        else:
            # 默认使用10MB分片
            return DEFAULT
    
    def _calculate_file_md5(self, file_path: str) -> str:
        """计算文件MD5"""
        hash_md5 = hashlib.md5()
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                hash_md5.update(chunk)
        return hash_md5.hexdigest().upper()
    
    def _calculate_slice_md5(self, file_path: str, offset: int, size: int) -> str:
        """计算分片MD5"""
        hash_md5 = hashlib.md5()
        with open(file_path, "rb") as f:
            f.seek(offset)
            remaining = size
            while remaining > 0:
                chunk_size = min(4096, remaining)
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                hash_md5.update(chunk)
                remaining -= len(chunk)
        return hash_md5.hexdigest().upper()
    
    def _init_upload(self, parent_id: str, filename: str, file_size: int, 
                    file_md5: str = None, slice_size: int = None, slice_md5: str = None, 
                    is_family: bool = False) -> Optional[Dict[str, Any]]:
        """初始化上传（参考Go源码request方法）"""
        if not self.is_login():
            print("未登录，无法初始化上传")
            return None
        
        if is_family:
            url = f"{UPLOAD_URL}/family/initMultiUpload"
        else:
            url = f"{UPLOAD_URL}/person/initMultiUpload"
        
        # 构建业务参数（需要加密）
        business_params = {
            'parentFolderId': parent_id,
            'fileName': quote(filename),
            'fileSize': str(file_size)
        }
        
        if file_md5:
            business_params['fileMd5'] = file_md5
        if slice_size:
            business_params['sliceSize'] = str(slice_size)
        if slice_md5:
            business_params['sliceMd5'] = slice_md5
        if is_family:
            business_params['familyId'] = self.family_id
        
        # 加密业务参数
        encrypted_params = self.encrypt_params(business_params, is_family)
        
        # 构建最终请求参数（包含客户端后缀和加密的业务参数）
        final_params = self.client_suffix()
        if encrypted_params:
            final_params['params'] = encrypted_params
        
        # 生成签名头（使用加密后的params）
        headers = self.signature_header(url, "GET", encrypted_params, is_family)
        
        if self.enable_debug:
            self.logger.debug(f"初始化上传 URL: {url}")
            self.logger.debug(f"初始化上传 业务参数: {business_params}")
            self.logger.debug(f"初始化上传 加密参数: {encrypted_params[:50]}..." if encrypted_params else "无加密参数")
            self.logger.debug(f"初始化上传 最终参数: {final_params}")
            self.logger.debug(f"初始化上传 请求头: {headers}")
        
        try:
            response = self.session.get(url, headers=headers, params=final_params)
            if self.enable_debug:
                self.logger.debug(f"初始化上传响应状态码: {response.status_code}")
                self.logger.debug(f"初始化上传响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                # 适配不同的响应格式
                if result.get('code') == 'SUCCESS' or result.get('res_code') == 0:
                    return result.get('data')
                else:
                    error_msg = result.get('msg') or result.get('res_message') or '未知错误'
                    print(f"初始化上传失败: {error_msg}")
        except Exception as e:
            print(f"初始化上传失败: {e}")
            if self.enable_debug:
                self.logger.error(f"初始化上传异常: {e}")
        return None
    
    def _get_upload_urls(self, upload_file_id: str, part_info: str, is_family: bool = False) -> Optional[List[Dict[str, Any]]]:
        """获取上传链接（参考Go源码request方法）"""
        if is_family:
            url = f"{UPLOAD_URL}/family/getMultiUploadUrls"
        else:
            url = f"{UPLOAD_URL}/person/getMultiUploadUrls"
        
        # 构建业务参数（需要加密）
        business_params = {
            'uploadFileId': upload_file_id,
            'partInfo': part_info
        }
        
        # 加密业务参数
        encrypted_params = self.encrypt_params(business_params, is_family)
        
        # 构建最终请求参数（包含客户端后缀和加密的业务参数）
        final_params = self.client_suffix()
        if encrypted_params:
            final_params['params'] = encrypted_params
        
        # 生成签名头（使用加密后的params）
        headers = self.signature_header(url, "GET", encrypted_params, is_family)
        
        if self.enable_debug:
            self.logger.debug(f"获取上传链接 URL: {url}")
            self.logger.debug(f"获取上传链接 业务参数: {business_params}")
            self.logger.debug(f"获取上传链接 加密参数: {encrypted_params[:50]}..." if encrypted_params else "无加密参数")
            self.logger.debug(f"获取上传链接 最终参数: {final_params}")
            self.logger.debug(f"获取上传链接 请求头: {headers}")
        
        try:
            response = self.session.get(url, headers=headers, params=final_params)
            if self.enable_debug:
                self.logger.debug(f"获取上传链接响应状态码: {response.status_code}")
                self.logger.debug(f"获取上传链接响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                # 适配不同的响应格式
                if result.get('code') == 'SUCCESS' or result.get('res_code') == 0:
                    # 对于getMultiUploadUrls接口，数据在uploadUrls字段中
                    upload_urls_data = result.get('uploadUrls') or result.get('data')
                    if upload_urls_data:
                        # 转换为列表格式
                        upload_urls = []
                        for part_key, url_info in upload_urls_data.items():
                            upload_urls.append({
                                'partNumber': int(part_key.replace('partNumber_', '')),
                                'requestURL': url_info['requestURL'],
                                'headers': self._parse_upload_headers(url_info.get('requestHeader', ''))
                            })
                        return upload_urls
                    return []
                else:
                    error_msg = result.get('msg') or result.get('res_message') or '未知错误'
                    print(f"获取上传链接失败: {error_msg}")
        except Exception as e:
            print(f"获取上传链接失败: {e}")
            if self.enable_debug:
                self.logger.error(f"获取上传链接异常: {e}")
        return None
    
    def _parse_upload_headers(self, header_str: str) -> Dict[str, str]:
        """解析上传请求头字符串"""
        headers = {}
        if not header_str:
            return headers
        
        # 解析格式："Content-Type=application/octet-stream&Authorization=AWS ..."
        for pair in header_str.split('&'):
            if '=' in pair:
                key, value = pair.split('=', 1)
                headers[key] = value
        return headers
    
    def _upload_part(self, upload_url: str, headers: Dict[str, str], file_path: str, 
                    offset: int, size: int) -> bool:
        """上传分片"""
        try:
            with open(file_path, 'rb') as f:
                f.seek(offset)
                data = f.read(size)
            
            if self.enable_debug:
                self.logger.debug(f"上传分片 URL: {upload_url}")
                self.logger.debug(f"上传分片 大小: {len(data)} bytes")
                self.logger.debug(f"上传分片 请求头: {headers}")
            
            response = self.session.put(upload_url, headers=headers, data=data)
            
            if self.enable_debug:
                self.logger.debug(f"上传分片响应状态码: {response.status_code}")
                self.logger.debug(f"上传分片响应内容: {response.text}")
            
            return response.status_code == 200
        except Exception as e:
            print(f"上传分片失败: {e}")
            if self.enable_debug:
                self.logger.error(f"上传分片异常: {e}")
            return False
    
    def _commit_upload(self, upload_file_id: str, file_md5: str = None, slice_md5: str = None, is_family: bool = False, overwrite: bool = True) -> Optional[Dict[str, Any]]:
        """提交上传（参考Go源码request方法）"""
        if is_family:
            url = f"{UPLOAD_URL}/family/commitMultiUploadFile"
        else:
            url = f"{UPLOAD_URL}/person/commitMultiUploadFile"
        
        # 构建业务参数（需要加密）
        business_params = {
            'uploadFileId': upload_file_id,
            'isLog': '0',
            'opertype': '3' if overwrite else '1'
        }
        
        # 添加MD5参数（如果提供）
        if file_md5:
            business_params['fileMd5'] = file_md5
        if slice_md5:
            business_params['sliceMd5'] = slice_md5
            business_params['lazyCheck'] = '1'  # 当有sliceMd5时需要lazyCheck
        
        # 加密业务参数
        encrypted_params = self.encrypt_params(business_params, is_family)
        
        # 构建最终请求参数（包含客户端后缀和加密的业务参数）
        final_params = self.client_suffix()
        if encrypted_params:
            final_params['params'] = encrypted_params
        
        # 生成签名头（使用加密后的params）
        headers = self.signature_header(url, "GET", encrypted_params, is_family)
        
        if self.enable_debug:
            self.logger.debug(f"提交上传 URL: {url}")
            self.logger.debug(f"提交上传 业务参数: {business_params}")
            self.logger.debug(f"提交上传 加密参数: {encrypted_params[:50]}..." if encrypted_params else "无加密参数")
            self.logger.debug(f"提交上传 最终参数: {final_params}")
            self.logger.debug(f"提交上传 请求头: {headers}")
        
        try:
            response = self.session.get(url, headers=headers, params=final_params)
            if self.enable_debug:
                self.logger.debug(f"提交上传响应状态码: {response.status_code}")
                self.logger.debug(f"提交上传响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                # 适配不同的响应格式
                if result.get('code') == 'SUCCESS' or result.get('res_code') == 0:
                    # 返回文件信息，优先返回file字段，其次data字段
                    return result.get('file') or result.get('data') or result
                else:
                    error_msg = result.get('msg') or result.get('res_message') or '未知错误'
                    print(f"提交上传失败: {error_msg}")
        except Exception as e:
            print(f"提交上传失败: {e}")
            if self.enable_debug:
                self.logger.error(f"提交上传异常: {e}")
        return None
    
    def upload_file(self, file_path: str, parent_id: str = "-11", is_family: bool = False, 
                   overwrite: bool = True, use_fast_upload: bool = True) -> Optional[Dict[str, Any]]:
        """上传文件
        
        Args:
            file_path: 本地文件路径
            parent_id: 父目录ID，默认为根目录
            is_family: 是否为家庭云
            overwrite: 是否覆盖同名文件
            use_fast_upload: 是否使用快传（先计算MD5检查是否已存在）
        
        Returns:
            上传成功返回文件信息，失败返回None
        """
        if not os.path.exists(file_path):
            print(f"文件不存在: {file_path}")
            return None
        
        if not self.is_login():
            print("未登录，无法上传文件")
            return None
        
        filename = os.path.basename(file_path)
        file_size = os.path.getsize(file_path)
        
        print(f"开始上传文件: {filename} ({file_size:,} bytes)")
        
        if use_fast_upload:
            # 尝试快传
            result = self.fast_upload(file_path, parent_id, is_family, overwrite)
            if result:
                return result
            print("快传失败，使用普通上传")
        
        # 普通上传
        return self.stream_upload(file_path, parent_id, is_family, overwrite)
    
    def fast_upload(self, file_path: str, parent_id: str = "-11", is_family: bool = False, 
                   overwrite: bool = True) -> Optional[Dict[str, Any]]:
        """快传上传（参考Go源码FastUpload）"""
        filename = os.path.basename(file_path)
        file_size = os.path.getsize(file_path)
        
        # 计算文件MD5和分片信息
        print("正在计算文件MD5...")
        file_md5 = self._calculate_file_md5(file_path)
        
        slice_size = self.calculate_slice_size(file_size)
        count = math.ceil(file_size / slice_size)
        
        # 计算分片MD5
        print("正在计算分片MD5...")
        slice_md5_list = []
        part_infos = []
        
        for i in range(count):
            offset = i * slice_size
            current_size = min(slice_size, file_size - offset)
            slice_md5 = self._calculate_slice_md5(file_path, offset, current_size)
            slice_md5_list.append(slice_md5)
            
            # 生成partInfo（格式：序号-base64编码的MD5）
            md5_bytes = bytes.fromhex(slice_md5)
            part_info = f"{i+1}-{base64.b64encode(md5_bytes).decode()}"
            part_infos.append(part_info)
        
        # 计算sliceMd5（如果只有一个分片则使用文件MD5，否则使用所有分片MD5的MD5）
        if count == 1:
            slice_md5 = file_md5
        else:
            slice_md5_str = "\n".join(slice_md5_list)
            slice_md5 = hashlib.md5(slice_md5_str.encode()).hexdigest().upper()
        
        # 初始化上传
        upload_info = self._init_upload(parent_id, filename, file_size, file_md5, slice_size, slice_md5, is_family)
        if not upload_info:
            return None
        
        # 检查文件是否已存在（快传）
        if upload_info.get('fileDataExists') == 1:
            print("文件已存在，使用快传")
            # 直接提交（快传不需要MD5参数）
            result = self._commit_upload(upload_info['uploadFileId'], None, None, is_family, overwrite)
            if result:
                print(f"快传成功: {filename}")
                return result
            else:
                print("快传提交失败")
                return None
        
        # 文件不存在，需要上传分片
        print(f"文件不存在，开始分片上传 ({count} 个分片)")
        
        # 并发上传分片
        max_workers = min(3, count)  # 限制并发数
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            futures = []
            
            for i, part_info in enumerate(part_infos):
                # 获取上传链接
                upload_urls = self._get_upload_urls(upload_info['uploadFileId'], part_info, is_family)
                if not upload_urls or len(upload_urls) == 0:
                    print(f"获取分片 {i+1} 上传链接失败")
                    return None
                
                upload_url_info = upload_urls[0]
                upload_url = upload_url_info['requestURL']
                upload_headers = upload_url_info.get('headers', {})
                
                offset = i * slice_size
                current_size = min(slice_size, file_size - offset)
                
                # 提交上传任务
                future = executor.submit(self._upload_part, upload_url, upload_headers, file_path, offset, current_size)
                futures.append((i+1, future))
            
            # 等待所有分片上传完成
            success_count = 0
            for part_num, future in futures:
                try:
                    if future.result():
                        success_count += 1
                        print(f"分片 {part_num}/{count} 上传成功")
                    else:
                        print(f"分片 {part_num}/{count} 上传失败")
                except Exception as e:
                    print(f"分片 {part_num}/{count} 上传异常: {e}")
        
        if success_count != count:
            print(f"分片上传失败，成功: {success_count}/{count}")
            return None
        
        # 提交上传
        print("所有分片上传完成，正在提交...")
        result = self._commit_upload(upload_info['uploadFileId'], file_md5, slice_md5, is_family, overwrite)
        if result:
            print(f"文件上传成功: {filename}")
            return result
        else:
            print("上传提交失败")
            return None
    
    def stream_upload(self, file_path: str, parent_id: str = "-11", is_family: bool = False, 
                     overwrite: bool = True) -> Optional[Dict[str, Any]]:
        """普通上传（参考Go源码StreamUpload）"""
        filename = os.path.basename(file_path)
        file_size = os.path.getsize(file_path)
        slice_size = self.calculate_slice_size(file_size)
        
        # 初始化上传（不提供MD5信息）
        upload_info = self._init_upload(parent_id, filename, file_size, None, slice_size, None, is_family)
        if not upload_info:
            return None
        
        count = math.ceil(file_size / slice_size)
        print(f"开始普通上传: {filename} ({count} 个分片)")
        
        # 计算文件MD5
        file_md5 = self._calculate_file_md5(file_path)
        slice_md5_list = []
        
        # 逐个上传分片
        for i in range(count):
            offset = i * slice_size
            current_size = min(slice_size, file_size - offset)
            
            # 计算当前分片MD5
            slice_md5 = self._calculate_slice_md5(file_path, offset, current_size)
            slice_md5_list.append(slice_md5)
            md5_bytes = bytes.fromhex(slice_md5)
            part_info = f"{i+1}-{base64.b64encode(md5_bytes).decode()}"
            
            # 获取上传链接
            upload_urls = self._get_upload_urls(upload_info['uploadFileId'], part_info, is_family)
            if not upload_urls or len(upload_urls) == 0:
                print(f"获取分片 {i+1} 上传链接失败")
                return None
            
            upload_url_info = upload_urls[0]
            upload_url = upload_url_info['requestURL']
            upload_headers = upload_url_info.get('headers', {})
            
            # 上传分片
            if self._upload_part(upload_url, upload_headers, file_path, offset, current_size):
                print(f"分片 {i+1}/{count} 上传成功")
            else:
                print(f"分片 {i+1}/{count} 上传失败")
                return None
        
        # 计算sliceMd5（参考Go代码逻辑）
        if file_size > slice_size:
            # 多个分片时，sliceMd5是所有分片MD5用换行符连接后的MD5
            slice_md5_str = "\n".join(slice_md5_list)
            slice_md5 = hashlib.md5(slice_md5_str.encode()).hexdigest().upper()
        else:
            # 单个分片时，sliceMd5等于fileMd5
            slice_md5 = file_md5
        
        # 提交上传
        print("所有分片上传完成，正在提交...")
        result = self._commit_upload(upload_info['uploadFileId'], file_md5, slice_md5, is_family, overwrite)
        if result:
            print(f"文件上传成功: {filename}")
            return result
        else:
            print("上传提交失败")
            return None
    
    def create_batch_task(self, task_type: str, family_id: str = "", target_folder_id: str = "", 
                         other: Dict[str, str] = None, *task_infos) -> Optional[Dict[str, Any]]:
        """创建批量任务（参考Go源码实现）"""
        if not self.is_login():
            print("未登录，无法创建批量任务")
            return None
        
        url = f"{API_URL}/batch/createBatchTask.action"
        is_family = bool(family_id)
        
        # 构建任务信息
        task_info_list = []
        for task_info in task_infos:
            if isinstance(task_info, dict):
                task_info_list.append(task_info)
            else:
                # 如果传入的是对象，转换为字典
                task_info_list.append({
                    'fileId': task_info.get('fileId', ''),
                    'fileName': task_info.get('fileName', ''),
                    'isFolder': task_info.get('isFolder', 0)
                })
        
        headers = self.signature_header(url, "POST", "", is_family)
        data = {
            'type': task_type,
            'taskInfos': json.dumps(task_info_list)
        }
        
        if target_folder_id:
            data['targetFolderId'] = target_folder_id
        
        if family_id:
            data['familyId'] = family_id
        
        if other:
            data.update(other)
        
        if self.enable_debug:
            self.logger.debug(f"创建批量任务 URL: {url}")
            self.logger.debug(f"创建批量任务 数据: {data}")
            self.logger.debug(f"创建批量任务 请求头: {headers}")
        
        try:
            response = self.session.post(url, headers=headers, data=data)
            if self.enable_debug:
                self.logger.debug(f"创建批量任务响应状态码: {response.status_code}")
                self.logger.debug(f"创建批量任务响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                if 'taskId' in result:
                    return result
                else:
                    print(f"创建批量任务失败: {result}")
        except Exception as e:
            print(f"创建批量任务失败: {e}")
        
        return None
    
    def check_batch_task(self, task_type: str, task_id: str) -> Optional[Dict[str, Any]]:
        """检查批量任务状态"""
        if not self.is_login():
            return None
        
        url = f"{API_URL}/batch/checkBatchTask.action"
        headers = self.signature_header(url, "POST", "", False)
        data = {
            'type': task_type,
            'taskId': task_id
        }
        
        try:
            response = self.session.post(url, headers=headers, data=data)
            if response.status_code == 200:
                return response.json()
        except Exception as e:
            if self.enable_debug:
                self.logger.error(f"检查批量任务状态失败: {e}")
        
        return None
    
    def wait_batch_task(self, task_type: str, task_id: str, sleep_time: float = 0.5, max_wait_time: int = 30) -> bool:
        """等待批量任务完成"""
        import time
        start_time = time.time()
        
        while time.time() - start_time < max_wait_time:
            state = self.check_batch_task(task_type, task_id)
            if not state:
                if self.enable_debug:
                    self.logger.debug(f"无法获取{task_type}任务状态: {task_id}")
                time.sleep(sleep_time)
                continue
            
            task_status = state.get('taskStatus', 0)
            if task_status == -1:  # 任务失败或不存在
                if self.enable_debug:
                    self.logger.error(f"{task_type}任务失败: {task_id}, 状态: {task_status}")
                return False
            elif task_status == 2:  # 存在冲突
                print("批量任务存在冲突")
                return False
            elif task_status == 4:  # 完成
                if self.enable_debug:
                    self.logger.debug(f"{task_type}任务完成: {task_id}")
                return True
            elif task_status in [0, 1, 3]:  # 0-初始化, 1-初始化, 3-执行中
                if self.enable_debug:
                    self.logger.debug(f"{task_type}任务进行中: {task_id}, 状态: {task_status}")
                time.sleep(sleep_time)
            else:
                if self.enable_debug:
                    self.logger.debug(f"{task_type}任务未知状态: {task_id}, 状态: {task_status}")
                time.sleep(sleep_time)
        
        if self.enable_debug:
            self.logger.debug(f"{task_type}任务等待超时: {task_id}")
        return False
    
    def get_conflict_task_info(self, task_type: str, task_id: str) -> Optional[Dict[str, Any]]:
        """获取冲突的任务信息（参考Go源码实现）"""
        if not self.is_login():
            print("未登录，无法获取冲突任务信息")
            return None
        
        url = f"{API_URL}/batch/getConflictTaskInfo.action"
        
        data = {
            'type': task_type,
            'taskId': task_id
        }
        
        try:
            response = self.session.post(url, data=data)
            if response.status_code == 200:
                result = response.json()
                if self.enable_debug:
                    self.logger.debug(f"获取冲突任务信息成功: {result}")
                return result
            else:
                print(f"获取冲突任务信息失败，状态码: {response.status_code}")
                return None
        except Exception as e:
            print(f"获取冲突任务信息异常: {e}")
            return None
    
    def manage_batch_task(self, task_type: str, task_id: str, target_folder_id: str, 
                         task_infos: List[Dict[str, Any]]) -> bool:
        """处理批量任务冲突（参考Go源码实现）"""
        if not self.is_login():
            print("未登录，无法处理批量任务冲突")
            return False
        
        url = f"{API_URL}/batch/manageBatchTask.action"
        
        data = {
            'targetFolderId': target_folder_id,
            'type': task_type,
            'taskId': task_id,
            'taskInfos': json.dumps(task_infos)
        }
        
        try:
            response = self.session.post(url, data=data)
            if response.status_code == 200:
                if self.enable_debug:
                    self.logger.debug("处理批量任务冲突成功")
                return True
            else:
                print(f"处理批量任务冲突失败，状态码: {response.status_code}")
                return False
        except Exception as e:
            print(f"处理批量任务冲突异常: {e}")
            return False
    
    def save_family_file_to_person_cloud(self, family_id: str, file_id: str, file_name: str, 
                                        is_folder: bool, target_folder_id: str, 
                                        overwrite: bool = False) -> bool:
        """保存家庭云中的文件到个人云（参考Go源码实现）"""
        if not self.is_login():
            print("未登录，无法保存家庭云文件到个人云")
            return False
        
        task_info = {
            'fileId': file_id,
            'fileName': file_name,
            'isFolder': 1 if is_folder else 0
        }
        
        other = {
            'groupId': 'null',
            'copyType': '2',
            'shareId': 'null'
        }
        
        # 创建复制任务
        resp = self.create_batch_task("COPY", family_id, target_folder_id, other, task_info)
        if not resp:
            print("创建保存任务失败")
            return False
        
        task_id = resp.get('taskId')
        if not task_id:
            print("获取任务ID失败")
            return False
        
        # 等待任务完成，处理冲突
        while True:
            state = self.check_batch_task("COPY", task_id)
            if not state:
                print("检查任务状态失败")
                return False
            
            task_status = state.get('taskStatus')
            
            if task_status == 2:  # 冲突
                task_info['dealWay'] = 3 if overwrite else 2  # 3=覆盖, 2=保留
                if not self.manage_batch_task("COPY", task_id, target_folder_id, [task_info]):
                    print("处理冲突失败")
                    return False
            elif task_status == 4:  # 完成
                print(f"保存成功: {file_name}")
                return True
            elif task_status == -1:  # 失败
                print(f"保存失败: {file_name}")
                return False
            
            time.sleep(0.4)  # 等待400毫秒
    
    def copy_file(self, file_id: str, file_name: str, is_folder: bool, 
                  target_folder_id: str, is_family: bool = False) -> bool:
        """复制文件或文件夹"""
        if not self.is_login():
            print("未登录，无法复制文件")
            return False
        
        family_id = self.family_id if is_family else ""
        other = {"targetFileName": target_folder_id}  # Go源码中的逻辑
        
        task_info = {
            'fileId': file_id,
            'fileName': file_name,
            'isFolder': 1 if is_folder else 0
        }
        
        # 创建复制任务
        resp = self.create_batch_task("COPY", family_id, target_folder_id, other, task_info)
        if not resp:
            print("创建复制任务失败")
            return False
        
        task_id = resp.get('taskId')
        if not task_id:
            print("获取任务ID失败")
            return False
        
        # 等待任务完成
        if self.wait_batch_task("COPY", task_id, 1.0):
            print(f"复制成功: {file_name}")
            return True
        else:
            print(f"复制失败: {file_name}")
            return False
    
    def delete_file(self, file_id: str, file_name: str, is_folder: bool, is_family: bool = False) -> bool:
        """删除文件或文件夹"""
        if not self.is_login():
            print("未登录，无法删除文件")
            return False
        
        family_id = self.family_id if is_family else ""
        
        task_info = {
            'fileId': file_id,
            'fileName': file_name,
            'isFolder': 1 if is_folder else 0
        }
        
        # 创建删除任务
        resp = self.create_batch_task("DELETE", family_id, "", None, task_info)
        if not resp:
            print("创建删除任务失败")
            return False
        
        task_id = resp.get('taskId')
        if not task_id:
            print("获取任务ID失败")
            return False
        
        # 等待任务完成（删除任务使用较短的等待时间）
        if self.wait_batch_task("DELETE", task_id, 0.2):
            print(f"删除成功: {file_name}")
            return True
        else:
            print(f"删除失败: {file_name}")
            return False
    
    def rename_file(self, file_id: str, new_name: str, is_folder: bool, is_family: bool = False) -> bool:
        """重命名文件或文件夹"""
        if not self.is_login():
            print("未登录，无法重命名文件")
            return False
        
        # 构建URL
        if is_family:
            base_url = f"{API_URL}/family/file"
            method = "GET"
        else:
            base_url = API_URL
            method = "POST"
        
        if is_folder:
            url = f"{base_url}/renameFolder.action"
            query_params = {
                'folderId': file_id,
                'destFolderName': new_name
            }
        else:
            url = f"{base_url}/renameFile.action"
            query_params = {
                'fileId': file_id,
                'destFileName': new_name
            }
        
        if is_family:
            query_params['familyId'] = self.family_id
        
        headers = self.signature_header(url, method, "", is_family)
        
        if self.enable_debug:
            self.logger.debug(f"重命名文件 URL: {url}")
            self.logger.debug(f"重命名文件 参数: {query_params}")
            self.logger.debug(f"重命名文件 请求头: {headers}")
        
        try:
            if method == "GET":
                response = self.session.get(url, headers=headers, params=query_params)
            else:
                response = self.session.post(url, headers=headers, data=query_params)
            
            if self.enable_debug:
                self.logger.debug(f"重命名文件响应状态码: {response.status_code}")
                self.logger.debug(f"重命名文件响应内容: {response.text}")
            
            if response.status_code == 200:
                result = response.json()
                # 检查是否成功
                if result.get('res_code') == 0 or 'id' in result or 'name' in result:
                    print(f"重命名成功: {new_name}")
                    return True
                else:
                    print(f"重命名失败: {result}")
        except Exception as e:
            print(f"重命名失败: {e}")
        
        return False
    
    def move_file(self, file_id: str, file_name: str, is_folder: bool, 
                  target_folder_id: str, is_family: bool = False) -> bool:
        """移动文件或文件夹"""
        if not self.is_login():
            print("未登录，无法移动文件")
            return False
        
        family_id = self.family_id if is_family else ""
        other = {"targetFileName": target_folder_id}  # Go源码中的逻辑
        
        task_info = {
            'fileId': file_id,
            'fileName': file_name,
            'isFolder': 1 if is_folder else 0
        }
        
        # 创建移动任务
        resp = self.create_batch_task("MOVE", family_id, target_folder_id, other, task_info)
        if not resp:
            print("创建移动任务失败")
            return False
        
        task_id = resp.get('taskId')
        if not task_id:
            print("获取任务ID失败")
            return False
        
        # 等待任务完成
        if self.wait_batch_task("MOVE", task_id, 0.4):
            print(f"移动成功: {file_name}")
            return True
        else:
            print(f"移动失败: {file_name}")
            return False

def main():
    """主函数 - 命令行交互界面"""
    client = Cloud189PCClient()
    
    print("=== 天翼云盘PC版客户端 ===")
    print("基于189pc Go代码实现的Python版本")
    print()
    
    # 检查登录状态
    if not client.is_login():
        print("未检测到有效的登录信息，需要重新登录")
        if not client.login():
            print("登录失败，程序退出")
            return
    else:
        print(f"已登录用户: {client.token_info.get('loginName', 'Unknown')}")
        # 验证登录状态
        if not client.refresh_session():
            print("登录状态已失效，需要重新登录")
            if not client.login():
                print("登录失败，程序退出")
                return
    
    # 主循环
    current_folder_id = "-11"  # 根目录
    is_family_mode = False
    
    while True:
        print(f"\n当前目录ID: {current_folder_id} ({'家庭云' if is_family_mode else '个人云'})")
        print("可用命令:")
        print("  ls [folder_id] - 列出文件和文件夹")
        print("  cd <folder_id> - 进入文件夹")
        print("  download <file_id> <file_name> - 下载文件")
        print("  upload <file_path> [folder_id] - 上传文件")
        print("  mkdir <folder_name> - 创建文件夹")
        print("  copy <file_id> <file_name> <is_folder> <target_folder_id> - 复制文件或文件夹")
        print("  delete <file_id> <file_name> <is_folder> - 删除文件或文件夹")
        print("  rename <file_id> <new_name> <is_folder> - 重命名文件或文件夹")
        print("  move <file_id> <file_name> <is_folder> <target_folder_id> - 移动文件或文件夹")
        print("  save_to_personal <file_id> <file_name> <is_folder> <target_folder_id> [overwrite] - 保存家庭云文件到个人云")
        print("  family - 切换到家庭云")
        print("  personal - 切换到个人云")
        print("  refresh - 刷新登录状态")
        print("  logout - 退出登录")
        print("  exit - 退出程序")
        
        try:
            command = input("\n请输入命令: ").strip().split()
            if not command:
                continue
            
            cmd = command[0].lower()
            
            if cmd == "exit":
                print("程序退出")
                break
            
            elif cmd == "logout":
                client.token_info = None
                client.identity = None
                if os.path.exists(client.cache_file):
                    os.remove(client.cache_file)
                print("已退出登录")
                if not client.login():
                    print("重新登录失败，程序退出")
                    break
            
            elif cmd == "refresh":
                if client.refresh_session():
                    print("登录状态刷新成功")
                    client.save_cache()
                else:
                    print("登录状态刷新失败")
            
            elif cmd == "family":
                is_family_mode = True
                current_folder_id = ""
                print("已切换到家庭云模式")
            
            elif cmd == "personal":
                is_family_mode = False
                current_folder_id = "-11"
                print("已切换到个人云模式")
            
            elif cmd == "ls":
                folder_id = command[1] if len(command) > 1 else current_folder_id
                result = client.get_files(folder_id, is_family_mode)
                if result:
                    file_list = result.get('fileListAO', {}).get('fileList', [])
                    folder_list = result.get('fileListAO', {}).get('folderList', [])
                    
                    print(f"\n文件夹 ({len(folder_list)}个):")
                    for folder in folder_list:
                        print(f"  [DIR]  {folder.get('id', '')} - {folder.get('name', '')}")
                    
                    print(f"\n文件 ({len(file_list)}个):")
                    for file in file_list:
                        size = int(file.get('size', 0))
                        size_str = f"{size:,} bytes"
                        if size > 1024*1024:
                            size_str = f"{size/(1024*1024):.2f} MB"
                        elif size > 1024:
                            size_str = f"{size/1024:.2f} KB"
                        print(f"  [FILE] {file.get('id', '')} - {file.get('name', '')} ({size_str})")
                else:
                    print("获取文件列表失败")
            
            elif cmd == "cd":
                if len(command) > 1:
                    current_folder_id = command[1]
                    print(f"已切换到目录: {current_folder_id}")
                else:
                    print("请指定目录ID")
            
            elif cmd == "download":
                if len(command) > 1:
                    file_id = command[1]
                    save_path = command[2] if len(command) > 2 else "./downloads"
                    if client.download_file(file_id, save_path, is_family_mode):
                        print("下载成功")
                    else:
                        print("下载失败")
                else:
                    print("请指定文件ID")
            
            elif cmd == "upload":
                if len(command) > 1:
                    file_path = command[1]
                    use_fast_upload = True
                    
                    # 检查参数
                    if "--stream" in command:
                        use_fast_upload = False
                        print("使用普通上传模式")
                    elif "--fast" in command:
                        use_fast_upload = True
                        print("使用快传模式")
                    
                    result = client.upload_file(file_path, current_folder_id, is_family_mode, True, use_fast_upload)
                    if result:
                        print(f"上传成功，文件ID: {result.get('id', 'Unknown')}")
                    else:
                        print("上传失败")
                else:
                    print("请指定文件路径")
            
            elif cmd == "mkdir":
                if len(command) > 1:
                    folder_name = " ".join(command[1:])
                    folder_id = client.make_dir(current_folder_id, folder_name, is_family_mode)
                    if folder_id:
                        print(f"文件夹创建成功，ID: {folder_id}")
                    else:
                        print("文件夹创建失败")
                else:
                    print("请指定文件夹名称")
            
            elif cmd == "copy":
                if len(command) >= 5:
                    file_id = command[1]
                    file_name = command[2]
                    is_folder = command[3].lower() in ['true', '1', 'folder']
                    target_folder_id = command[4]
                    
                    if client.copy_file(file_id, file_name, is_folder, target_folder_id, is_family_mode):
                        print("复制操作成功")
                    else:
                        print("复制操作失败")
                else:
                    print("用法: copy <file_id> <file_name> <is_folder> <target_folder_id>")
                    print("示例: copy 123456 test.txt false 789012")
                    print("示例: copy 123456 MyFolder true 789012")
            
            elif cmd == "delete":
                if len(command) >= 4:
                    file_id = command[1]
                    file_name = command[2]
                    is_folder = command[3].lower() in ['true', '1', 'folder']
                    
                    confirm = input(f"确认删除 {'文件夹' if is_folder else '文件'} '{file_name}'? (y/N): ")
                    if confirm.lower() == 'y':
                        if client.delete_file(file_id, file_name, is_folder, is_family_mode):
                            print("删除操作成功")
                        else:
                            print("删除操作失败")
                    else:
                        print("取消删除操作")
                else:
                    print("用法: delete <file_id> <file_name> <is_folder>")
                    print("示例: delete 123456 test.txt false")
                    print("示例: delete 123456 MyFolder true")
            
            elif cmd == "rename":
                if len(command) >= 4:
                    file_id = command[1]
                    new_name = command[2]
                    is_folder = command[3].lower() in ['true', '1', 'folder']
                    
                    if client.rename_file(file_id, new_name, is_folder, is_family_mode):
                        print("重命名操作成功")
                    else:
                        print("重命名操作失败")
                else:
                    print("用法: rename <file_id> <new_name> <is_folder>")
                    print("示例: rename 123456 new_name.txt false")
                    print("示例: rename 123456 NewFolderName true")
            
            elif cmd == "move":
                if len(command) >= 5:
                    file_id = command[1]
                    file_name = command[2]
                    is_folder = command[3].lower() in ['true', '1', 'folder']
                    target_folder_id = command[4]
                    
                    if client.move_file(file_id, file_name, is_folder, target_folder_id, is_family_mode):
                        print("移动操作成功")
                    else:
                        print("移动操作失败")
                else:
                    print("用法: move <file_id> <file_name> <is_folder> <target_folder_id>")
                    print("示例: move 123456 test.txt false 789012")
                    print("示例: move 123456 MyFolder true 789012")
            
            elif cmd == "save_to_personal":
                if len(command) >= 5:
                    file_id = command[1]
                    file_name = command[2]
                    is_folder = command[3].lower() in ['true', '1', 'folder']
                    target_folder_id = command[4]
                    overwrite = len(command) > 5 and command[5].lower() in ['true', '1', 'overwrite']
                    
                    if not is_family_mode:
                        print("此功能仅在家庭云模式下可用，请先切换到家庭云")
                    elif not client.family_id:
                        print("未获取到家庭云ID，请检查家庭云配置")
                    else:
                        if client.save_family_file_to_person_cloud(client.family_id, file_id, file_name, is_folder, target_folder_id, overwrite):
                            print("保存到个人云成功")
                        else:
                            print("保存到个人云失败")
                else:
                    print("用法: save_to_personal <file_id> <file_name> <is_folder> <target_folder_id> [overwrite]")
                    print("示例: save_to_personal 123456 test.txt false -11 false")
                    print("示例: save_to_personal 123456 MyFolder true -11 true")
                    print("注意: 此命令仅在家庭云模式下可用")
            
            else:
                print(f"未知命令: {cmd}")
        
        except KeyboardInterrupt:
            print("\n程序被中断")
            break
        except Exception as e:
            print(f"执行命令时出错: {e}")

if __name__ == "__main__":
    main()